﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Threading;
using System.Collections.Generic;
using ttg_internal.Utilities.PipelineManager.Diagram;
using ttg_internal.utils;

namespace ttg_internal.Utilities.PipelineManager.Helpers
{
    /// <summary>
    /// Dynamically updated converted collection
    /// </summary>
    internal class NodesCollectionConverter : ReadOnlyObservableCollection<UIElement>, IWeakEventListener
    {
        /// <summary>
        /// Initializes a new instance
        /// </summary>
        public NodesCollectionConverter(Dispatcher dispatcher, IEnumerable<Node> nodeCollection)
            : base(new ObservableCollection<UIElement>())
        {
            if (dispatcher == null)
            {
                throw new ArgumentNullException("dispatcher");
            }

            _dispatcher = dispatcher;
            _nodeCollection = nodeCollection;

            RefreshCollection();

            var observable = _nodeCollection as INotifyCollectionChanged;

            if (observable != null)
            {
                CollectionChangedEventManager.AddListener(observable, this);
            }
        }

        // Private fields
        private readonly Dispatcher _dispatcher;
        private readonly Dictionary<Node, DesignerItem> _itemsMap = new Dictionary<Node, DesignerItem>();
        private readonly IEnumerable<Node> _nodeCollection;
        private int _isRefreshing;

        /// <summary>
        /// Refreshes collection
        /// </summary>
        private void RefreshCollection()
        {
            if (Interlocked.CompareExchange(ref _isRefreshing, 1, 0) == 1)
            {
                throw new InvalidOperationException("Refresh is already in progress");
            }

            Items.Clear();
            _itemsMap.Clear();

            foreach (Node node in _nodeCollection)
            {
                var contentListBox = new ListBox { IsHitTestVisible = false, MinHeight = 40, MinWidth = 60 };
                contentListBox.Items.Add(BindingHelper.SetBinding(new ListBoxItem(), ContentControl.ContentProperty, new Binding("Name") { Source = node }));
                contentListBox.Items.Add(BindingHelper.SetBinding(new ListBoxItem(), ContentControl.ContentProperty, new Binding("ActiveTaskCount") { Source = node }));
                contentListBox.Items.Add(BindingHelper.SetBinding(new ListBoxItem(), ContentControl.ContentProperty, new Binding("Usage") { Source = node }));

                var designerItem = new DesignerItem { Content = contentListBox, DataContext = node };
                _itemsMap.Add(node, designerItem);

                CollectionChangedEventManager.AddListener(node.Outputs, this);
            }

            foreach (DesignerItem element in _itemsMap.Values)
            {
                Items.Add(element);
            }

            foreach (Connection element in _nodeCollection.SelectMany(source => source.Outputs, (source, target) => new Connection(_itemsMap[source], _itemsMap[target]) { DataContext = source }))
            {
                Items.Add(element);
            }

            _isRefreshing = 0;
        }

        /// <summary>
        /// Receives events from the centralized event manager.
        /// </summary>
        bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
        {
            if (_dispatcher.CheckAccess())
            {
                RefreshCollection();
            }
            else
            {
                _dispatcher.BeginInvoke(new Action(RefreshCollection));
            }

            return true;
        }
    }

    internal static class BindingHelper
    {
        public static T SetBinding<T>(this T obj, DependencyProperty prop, BindingBase binding)
            where T : DependencyObject
        {
            BindingOperations.SetBinding(obj, prop, binding);
            return obj;
        }
    }
}
